package com.dycong.common.IO.netty;

import io.netty.buffer.*;
import io.netty.util.ByteProcessor;
import io.netty.util.ReferenceCountUtil;
import org.junit.Test;

import java.lang.ref.SoftReference;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;

/**
 * Created by dycong on 2017/3/14.
 */
public class netty_ByteBuf {
    /**
     * writeXxx(xxx value) 这组方法将不同类型的数据写到buf里，同时将writerIndex往前移适当的距离
     * readXxx() 这组方法从buf里读出某种类型的数据，同时将readerIndex往前移适当的距离
     * skipBytes(int length) 将readerIndex往前移指定的距离
     * setXxx(int index, xxx value) 这组方法将不同类型的数据写到buf的指定位置
     * getXxx(int index) 这组方法从buf的指定位置读出一个某种类型的数据
     * readerIndex()/writerIndex() 访问readerIndex和writerIndex
     * readerIndex(int)/writerIndex(int) 设置readerIndex和writerIndex
     * readableBytes() 返回可读区域的字节数
     * writableBytes() 返回可写区域的字节数
     * clear() 清除buf（把readerIndex和writerIndex都设为0）
     * discardReadBytes() 扔掉已读数据
     */
    //todo 默认初始值private static final int DEFAULT_INITIAL_CAPACITY = 256;
    //static final int DEFAULT_MAX_COMPONENTS = 16;

    /*
todo Mark和Reset操作：
todo mark和reset操作主要用于读写发生错误时回滚，回滚的作用主要就是重置位置索引。
todo 由于ByteBuf有读写两个指针，所以有四个相关的方法：

todo markReaderIndex ： 将当前readerIndex赋值给markedReaderIndex中
todo resetReaderIndex ： 将当前readerIndex设置为markedReaderIndex
todo markWriterIndex ： 将当前writerIndex赋值给markedWriterIndex中
todo resetWriterIndex ： 将当前writerIndex设置为markedWriterIndex
     */
    public static void main(String[] args) {

        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();

        ByteBuf byteBuf2 = ByteBufAllocator.DEFAULT.buffer(1024, 2048);

        ByteBuf byteBuf3 = ByteBufAllocator.DEFAULT.heapBuffer();
        ByteBuf byteBuf4 = ByteBufAllocator.DEFAULT.heapBuffer(1024);
        ByteBuf byteBuf5 = ByteBufAllocator.DEFAULT.heapBuffer(1024, 2048);

        ByteBuf byteBuf6 = ByteBufAllocator.DEFAULT.ioBuffer();

        ByteBuf byteBuf8 = ByteBufAllocator.DEFAULT.ioBuffer(1024, 2048);

        ByteBuf byteBuf9 = ByteBufAllocator.DEFAULT.directBuffer();
        ByteBuf byteBuf10 = ByteBufAllocator.DEFAULT.directBuffer(1024);
        ByteBuf byteBuf11 = ByteBufAllocator.DEFAULT.directBuffer(1024, 2048);

        ByteBuf byteBuf12 = ByteBufAllocator.DEFAULT.compositeBuffer();
        ByteBuf byteBuf13 = ByteBufAllocator.DEFAULT.compositeBuffer(1024);


        ByteBuf byteBuf16 = ByteBufAllocator.DEFAULT.compositeDirectBuffer(1024);

        ByteBuf byteBuf17 = ByteBufAllocator.DEFAULT.compositeHeapBuffer();

        // TODO: 2017/4/11 内存池 的堆缓冲区
        ByteBuf b = PooledByteBufAllocator.DEFAULT.heapBuffer();
        // TODO: 2017/4/11 内存池 的直接内存缓冲区
        ByteBuf b1 = PooledByteBufAllocator.DEFAULT.directBuffer();
        ByteBuf byteBuf1 = ByteBufAllocator.DEFAULT.buffer(1024);//默认内存缓冲区
        ByteBuf byteBuf7 = ByteBufAllocator.DEFAULT.ioBuffer(1024);
        ByteBuf byteBuf15 = ByteBufAllocator.DEFAULT.compositeDirectBuffer(1024);

        // TODO: 2017/4/11 内非存池 的堆缓冲区
        ByteBuf b2 = UnpooledByteBufAllocator.DEFAULT.heapBuffer();
        // TODO: 2017/4/11 内非存池 的直接内存缓冲区
        ByteBuf b3 = UnpooledByteBufAllocator.DEFAULT.directBuffer();
        ByteBuf b4 = UnpooledByteBufAllocator.DEFAULT.buffer();
        ByteBuf b5 = UnpooledByteBufAllocator.DEFAULT.ioBuffer();
        ByteBuf b6 = UnpooledByteBufAllocator.DEFAULT.compositeDirectBuffer();

        // buffer() false
        System.out.println(byteBuf.hasArray());
        // heapBuffer() true
        System.out.println(byteBuf3.hasArray());
        byte[] bytes = byteBuf3.array();
        int offset = byteBuf3.arrayOffset() + byteBuf.readerIndex();
        int length = byteBuf.readableBytes();
        // ioBuffer() false
        System.out.println(byteBuf6.hasArray());
        // directBuffer() false
        System.out.println(byteBuf9.hasArray());
        // compositeBuffer() true
        System.out.println(byteBuf12.hasArray());
        /*
        todo 一个CompositeByteBuf中的ByteBuf实例可能同时包含direct和非direct分配，
        todo 如果只有一个ByteBuf实例，那么在CompositeByteBuf上调用hasArray()会返回那个ByteBuf的hasArray()值；否则，会返回false。
         */

        //实现Composite buffer模式
        ByteBuffer header = ByteBuffer.allocate(128);
        ByteBuffer body = ByteBuffer.allocate(128);
        ByteBuffer message = ByteBuffer.allocate(header.remaining() + body.remaining());
        header.flip();
        body.flip();
        message.put(header);
        message.put(body);
        message.flip();

        CompositeByteBuf compositeBuffer = (CompositeByteBuf) ByteBufAllocator.DEFAULT.compositeBuffer();
        ByteBuf headerBuf = ByteBufAllocator.DEFAULT.buffer();       //可以一试hasArray(),direct Buffer
        ByteBuf bodyBuf = ByteBufAllocator.DEFAULT.heapBuffer();         //可以一试hasArray(),direct Buffer

        compositeBuffer.addComponents(headerBuf, bodyBuf);
        compositeBuffer.removeComponent(0);     //删除headerBuf compositeBuffer也是有数组的
        for (ByteBuf buf : compositeBuffer) {      //todo 遍历所有的ByteBuf实例
            System.out.println(buf.toString());
        }

        for (int i = 0; i < 10; i++) {
            byteBuf.writeByte(i);
        }
        for (int i = 0; i < 10; i++) {
            byteBuf1.writeByte(i);
        }
        //todo 从一个CompositeByteBuf存取数据
        CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
        compositeByteBuf.addComponents(byteBuf, byteBuf1);
        for (ByteBuf buf : compositeByteBuf) {       //遍历所有的ByteBuf实例
            System.out.println(buf.toString());
        }

        CompositeByteBuf compositeByteBuf1 = Unpooled.compositeBuffer();
        compositeByteBuf1.writeByte(1);
        length = compositeBuffer.readableBytes();
        byte[] bytes1 = new byte[length];
        compositeByteBuf.getBytes(compositeByteBuf.readableBytes(), bytes1);

        //todo DEFAULT pool
        //todo 获取数据采用的方法用一个索引作为一个输入参数，并不改变readerIndex或者writerIndex的值。
        // todo 如果需要，它们的值可以通过调用readerIndex(index)或者writerIndex(index)手动修改。
        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
        for (int i = 0; i < buf.capacity(); i++) {
            byte by = buf.getByte(i);
        }
        buf.discardReadBytes();//类似ByteBuffer的compact()

        //todo  读所有的数据
        while (buf.isReadable()) {
            buf.readByte();
        }

        //todo 填满一个buffer直到空间用完
        while (buf.writableBytes() >= 4)
            buf.writeInt(1);


        //todo 有好几种方法可以获得ByteBuf中某个值的索引
        int index = byteBuf.indexOf(0, bodyBuf.writableBytes(), new Byte("1"));
        int index1 = byteBuf.forEachByte(ByteProcessor.FIND_NON_NUL);

        /*
        todo 每个方法都返回一个新的ByteBuf实例，包含该实例自己的reader，writer和标记索引。
        todo 和JDK ByteBuffer一样，内部的存储是和源ByteBuf共享的。这让派生buffer的创建开销不是不大，
        todo 但是也意味着如果你改变了派生buffer的内容，你同时也在改变源buffer实例(source instance)的内容，
        todo 所以使用时要小心。

        todo  BYTEBUF COPYING  如果你需要的是一个现存buffer的拷贝，那么用copy()或者copy(int, int)。和一个派生的buffer不同，
        todo 这些调用返回的ByteBuf有一份独立（包含独立的内容和索引）的数据拷贝。
        todo they maintain separate indexes and marks.
         */
        byteBuf1.duplicate();
        //相当于
        byteBuf1.slice(0, byteBuf1.capacity());
        Unpooled.unmodifiableBuffer(bodyBuf, byteBuf1);
        //starting at the current
        // {@code readerIndex} and increases the {@code readerIndex}
        bodyBuf.readSlice(5);

        ByteBuf copy = bodyBuf.copy(0, 52);


        /*
        todo JDK的InputStream定义了方法mark(int readlimit) 和reset()。这两个方法分别用来标记stream的当前位置为一个指定值，或重置当前stream到这个指定位置。
        todo 同样地，你可以通过调用markReaderIndex()，markWriterIndex(), resetReaderIndex()和resetWriterIndex()设置和重置ByteBuf readerIndex和writerIndex。
        todo 这些方法和InputStream的类似，除了它们没有readlimit参数来指定何时标记会失效
         */

        bodyBuf.markReaderIndex();
        bodyBuf.markWriterIndex();
//        bodyBuf.resetReaderIndex();
//        bodyBuf.resetWriterIndex();

        /*
        像我们提到过的，有两类读/写操作：

        todo get()和set()操作从一个指定的索引开始操作，不改变索引的值
        todo read()和write()操作从一个指定的索引开始操作，随着读/写的字节数调整索引值
         */

        ByteBuf byteBuf14 = Unpooled.copiedBuffer("".toCharArray(), 0, 22, Charset.forName("utf-8"));

        //todo 它将一个ByteBuf内容以十六进制字符串的方式打印出来
        String str = ByteBufUtil.hexDump(byteBuf4);
        ByteBufUtil.swapInt(1);
        /*
        Netty 4 引入了 引用计数器给 ByteBuf 和 ByteBufHolder（两者都实现了 ReferenceCounted 接口）
        引用计数本身并不复杂;它在特定的对象上跟踪引用的数目。实现了ReferenceCounted 的类的实例会通常开始于一个活动的引用计数器为 1。活动的引用计数器大于0的对象被保证不被释放。当数量引用减少到0，该实例将被释放。需要注意的是“释放”的语义是特定于具体的实现。最起码，一个对象，它已被释放应不再可用。
        这种技术就是诸如 PooledByteBufAllocator 这种减少内存分配开销的池化的精髓部分。
        Listing 5.16 Reference counting
        Channel channel = ...;
        ByteBufAllocator allocator = channel.alloc(); //1
        ....
        ByteBuf buffer = allocator.directBuffer(); //2
        assert buffer.refCnt() == 1; //3
        ...
        1.从 channel 获取 ByteBufAllocator
        2.从 ByteBufAllocator 分配一个 ByteBuf
        3.检查引用计数器是否是 1
        Listing 5.17 Release reference counted object
        ByteBuf buffer = ...;
        boolean released = buffer.release(); //1
        ...
        1.release（）将会递减对象引用的数目。当这个引用计数达到0时，对象已被释放，并且该方法返回 true。
        如果尝试访问已经释放的对象，将会抛出 IllegalReferenceCountException 异常。
        需要注意的是一个特定的类可以定义自己独特的方式其释放计数的“规则”。 例如，release() 可以将引用计数器直接计为 0 而不管当前引用的对象数目。
        谁负责 release？
        在一般情况下，最后访问的对象负责释放它。
         */
        ByteBufAllocator bufAllocator = ByteBufAllocator.DEFAULT;
        ByteBuf b7 = bufAllocator.buffer();
        ByteBuf b8 = bufAllocator.buffer();
        ByteBuf b9 = b1.duplicate();
        System.out.println(b1.refCnt());
        System.out.println(b1 == b2);
        int i = 0;
        //如果你有疑虑，或者你想简化释放消息内存的过程，你可以使用ReferenceCountUtil.release():
        ReferenceCountUtil.release(b7);
        b1.retain();
//        while (!b1.release())
//            b1.release();
        System.gc();
        SoftReference softReference = new SoftReference("");
    }

    @Test
    public void test() {
        ByteBuf b3 = UnpooledByteBufAllocator.DEFAULT.directBuffer();
        b3.writeByte(1);
        b3.writeByte(1);
        b3.writeByte(1);
        System.out.println(b3.writableBytes());

        for (int i=0;i<b3.writableBytes();i++){
            System.out.println(b3.getByte(i));
        }
    }
}
